/*
*  This program is free software: you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation, either version 3 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/* This plugin allow all infected to steal victim on all other infected */

#pragma semicolon 1
#include <sourcemod>
#include <sdktools>
#include <l4d_stocks>
#include <sdkhooks>

#include "macros.sp"
#include "helpers.sp"

new owned_array[MAXCLIENTS] = { -1, ... };
new Handle:cvar_timereload_after_steal;
#define STEAL_SOUND "player/charger/hit/charger_smash_02.wav"

public OnPluginStart()
{

	/* Event to track who own who */
	/*charger*/
	HookEvent("charger_pummel_start", event_inf_grab);
	HookEvent("charger_pummel_end", event_inf_release);
	/*smoker*/
	HookEvent("tongue_grab", event_inf_grab);
	HookEvent("tongue_release", event_inf_release);
	/*hunter*/
	HookEvent("lunge_pounce", event_inf_grab);
	HookEvent("lunge_shove", event_inf_release);
	HookEvent("pounce_stopped", event_inf_release);
	/*jockey*/
	HookEvent("jockey_ride", event_inf_grab);
	HookEvent("jockey_ride_end", event_inf_release);

	/* in all case */
	HookEvent("player_death", event_inf_release);
	HookEvent("player_bot_replace", event_swap_bot_to_player);
	HookEvent("bot_player_replace", event_swap_player_to_bot);

	/* event to start and stop hooking */
	HookEvent("charger_charge_start", event_charger_start);
	HookEvent("charger_carry_start", event_charger_end);
	HookEvent("charger_charge_end", event_charger_end);

	HookEvent("round_start", event_round_start);

	cvar_timereload_after_steal = CreateConVar("l4d2_chargerfix_time_reload_ability", "5.0", "Time to reload ability after steal");
}

public OnMapStart()
{
	PrecacheSound(STEAL_SOUND);

}

clear_owner_array()
{
	for(new i=0; i < MAXCLIENTS; i++)
	{
		owned_array[i] = -1;
	}
}

get_index_for(surv)
{
	for(new i=0; i < MAXCLIENTS; i++)
	{
		if(owned_array[i] == surv)
			return i;
	}

	return -1;
}

public Action:event_round_start(Handle:event, const String:name[], bool:dontBroadcast)
{
	clear_owner_array();
}

/* track ownage */
public Action:event_inf_grab(Handle:event, const String:name[], bool:dontBroadcast)
{
	new client = GetClientOfUserId(GetEventInt(event, "userid"));
	new victim = GetClientOfUserId(GetEventInt(event, "victim"));

	if(IsValidInfected(client) && IsValidSurvivor(victim))
	{
		owned_array[client] = victim;
	}
}

public Action:event_inf_release(Handle:event, const String:name[], bool:dontBroadcast)
{
	new client = GetClientOfUserId(GetEventInt(event, "userid"));

	if(IsValidInfected(client))
	{
		owned_array[client] = -1;
	}
}

/*bot replace a player, check if player was grab, if yes, do the replacement */
public Action:event_swap_bot_to_player(Handle:event, const String:name[], bool:dontBroadcast)
{
	new player = GetClientOfUserId(GetEventInt(event, "player"));
	new bot = GetClientOfUserId(GetEventInt(event, "bot"));

	/* check if player was grab */
	new pindex = get_index_for(player);

	if(pindex != -1)
		owned_array[pindex] = bot;
}

/* player replace a bot, check if bot was grab, if yes, do the replacement */
public Action:event_swap_player_to_bot(Handle:event, const String:name[], bool:dontBroadcast)
{
	new player = GetClientOfUserId(GetEventInt(event, "player"));
	new bot = GetClientOfUserId(GetEventInt(event, "bot"));

	/* check if bot was grab */
	new bindex = get_index_for(bot);

	if(bindex != -1)
		owned_array[bindex] = player;
}


/* start and stop hook */
public Action:event_charger_start(Handle:event, const String:name[], bool:dontBroadcast)
{
	new client = GetClientOfUserId(GetEventInt(event, "userid"));

	if(IsValidInfected(client) && L4D2_GetPlayerZombieClass(client) == L4D2ZombieClass_Charger)
	{
		SDKHook(client, SDKHook_StartTouch, OnChargerTouch);
	}
}

public Action:event_charger_end(Handle:event, const String:name[], bool:dontBroadcast)
{
	new client = GetClientOfUserId(GetEventInt(event, "userid"));

	if(IsValidInfected(client) && L4D2_GetPlayerZombieClass(client) == L4D2ZombieClass_Charger)
	{
		SDKUnhook(client, SDKHook_StartTouch, OnChargerTouch);	
	}
}


public Action:OnChargerTouch(charger, client)
{
	if(IsValidInfected(charger) && L4D2_GetPlayerZombieClass(charger) == L4D2ZombieClass_Charger)
	{
		
		/* if already got a victim, just return */
		if(L4D2_GetSurvivorVictim(charger) != -1)
			return;

		/*2 case : 1/ Touching survivors, 2 touching infected who got a survivor */

		/*touching a survivor */
		if(IsValidSurvivorAlive(client))
		{			
			/* Check if this surv is already taken */
			new attacker = get_index_for(client);
			
			/* if attacker == -1, the charger will grab it normally*/
			/* if attacker == charger, that mean there is a problem, but do nothing more ^^ */
			if(attacker != -1 && attacker != charger)
			{
				/* steal the surv from the other inf */
				sub_debug("stealing %N from %N", client, attacker);

				owned_array[attacker] = -1;
				owned_array[charger] = client;
				
				set_carry_to_victim(client, charger);
				set_attacker_free(attacker);

				/*set charger prop*/
				SetEntPropEnt(charger, Prop_Send, "m_carryVictim", client);
				EmitSoundToAll(STEAL_SOUND, charger);
			}
		}
		else if(IsValidInfectedAlive(client)) //touching an infected
		{
			new victim = owned_array[client];

			if(victim != -1)
			{

				/* steal the surv from the other inf */
				sub_debug("stealing %N from %N", victim, client);

				owned_array[client] = -1;
				owned_array[charger] = victim;
				set_carry_to_victim(victim, charger);
				set_attacker_free(client);
				
				/*set charger prop*/
				SetEntPropEnt(charger, Prop_Send, "m_carryVictim", client);					
				EmitSoundToAll(STEAL_SOUND, charger);
			}
		}
	}
}


set_carry_to_victim(victim, attacker)
{
	/*set client prop*/
	SetEntPropEnt(victim, Prop_Send, "m_pummelAttacker", -1);
	SetEntPropEnt(victim, Prop_Send, "m_carryAttacker", attacker);
	SetEntPropEnt(victim, Prop_Send, "m_pounceAttacker", -1);
	SetEntPropEnt(victim, Prop_Send, "m_tongueOwner", -1);
	SetEntPropEnt(victim, Prop_Send, "m_jockeyAttacker", -1);
}

set_attacker_free(attacker)
{
	/* set infected prop */
	switch(L4D2_GetPlayerZombieClass(attacker))
	{
		case L4D2ZombieClass_Smoker:
		{
			SetEntPropEnt(attacker, Prop_Send, "m_tongueVictim", -1);
		}
		case L4D2ZombieClass_Hunter:
		{
			SetEntPropEnt(attacker, Prop_Send, "m_pounceVictim", -1);
		}
		case L4D2ZombieClass_Jockey:
		{
			SetEntPropEnt(attacker, Prop_Send, "m_jockeyVictim", -1);
		}
		case L4D2ZombieClass_Charger:
		{
			SetEntPropEnt(attacker, Prop_Send, "m_pummelVictim", -1);
			SetEntPropEnt(attacker, Prop_Send, "m_carryVictim", -1);			
		}
	}

	ResetInfectedAbility(attacker, GetConVarFloat(cvar_timereload_after_steal));
}